1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 12 module hip.systems.game; 13 import hip.audio; 14 import hip.view; 15 import hip.error.handler; 16 import hip.api.view.scene; 17 18 import hip.systems.timer_manager; 19 import hip.event.dispatcher; 20 import hip.event.handlers.keyboard; 21 import hip.event.handlers.mouse; 22 import hip.windowing.events; 23 import hip.hiprenderer.renderer; 24 import hip.graphics.g2d.renderer2d; 25 public import hip.event.handlers.input_listener; 26 27 version(WebAssembly) version = CustomRuntime; 28 version(CustomRuntimeTest) version = CustomRuntime; 29 version(PSVita) version = CustomRuntime; 30 31 version(Standalone) 32 { 33 pragma(mangle, "HipremeEngineMainScene") 34 extern(C) AScene HipremeEngineMainScene(); 35 } 36 37 version(Load_DScript) 38 { 39 import hip.systems.hotload; 40 import hip.systems.compilewatcher; 41 private bool getDubError(string line) 42 { 43 import hip.console.log; 44 import hip.util.string:indexOf, lastIndexOf; 45 int errInd = line.indexOf("Warning:"); 46 if(errInd == -1) errInd = line.indexOf("Error:"); //Check first for warnings 47 return errInd != -1; 48 } 49 extern(System) AScene function() HipremeEngineGameInit; 50 extern(System) void function() HipremeEngineGameDestroy; 51 } 52 53 54 class GameSystem 55 { 56 /** 57 * Holds the member that generates the events as inputs 58 */ 59 EventDispatcher dispatcher; 60 AScene[] scenes; 61 62 HipInputListener inputListener; 63 HipInputListener scriptInputListener; 64 string projectDir, buildCommand = "redub build"; 65 ///Resets delta time after a reload for not jumping frames. 66 protected bool shouldResetDelta; 67 protected __gshared AScene externalScene; 68 69 version(Load_DScript) 70 { 71 static CompileWatcher watcher; 72 protected static HotloadableDLL hotload; 73 } 74 bool hasFinished; 75 bool isInUpdate; 76 float fps = 0; 77 float targetFPS = 0; 78 float fpsAccumulator = 0; 79 size_t frames = 0; 80 81 this(float targetFPS) 82 { 83 this.targetFPS = targetFPS; 84 dispatcher = new EventDispatcher(HipRenderer.window, &this.isInUpdate); 85 dispatcher.addOnResizeListener((uint width, uint height) 86 { 87 HipRenderer.width = width; 88 HipRenderer.height = height; 89 resizeRenderer2D(width, height); 90 foreach (AScene s; scenes) 91 s.onResize(width, height); 92 }); 93 inputListener = new HipInputListener(dispatcher); 94 scriptInputListener = new HipInputListener(dispatcher); 95 96 import hip.console.log; 97 inputListener.addKeyboardListener(HipKey.ESCAPE, 98 (meta){hasFinished = true;} 99 ); 100 // inputListener.addKeyboardListener(HipKey.F1, 101 // (meta){import hip.bind.interpreters; reloadInterpreter();}, 102 // HipButtonType.up 103 // ); 104 105 version(Load_DScript) 106 { 107 inputListener.addKeyboardListener(HipKey.F5,(meta) 108 { 109 import hip.console.log; 110 rawlog("Recompiling and Reloading game "); 111 recompileReloadExternalScene(); 112 }, HipButtonType.up 113 ); 114 } 115 import hip.api.input.core; 116 setHipInput(dispatcher); 117 setHipInputListener(scriptInputListener); 118 119 } 120 121 void loadGame(string gameDll, string buildCommand) 122 { 123 version(Load_DScript) 124 { 125 import hip.filesystem.hipfs; 126 import hip.util.path; 127 import hip.util.system; 128 import hip.util.string:indexOf; 129 import hip.console.log; 130 131 132 if(gameDll.isAbsolutePath && HipFS.absoluteIsFile(gameDll)) 133 { 134 projectDir = gameDll.dirName; 135 } 136 else if(!gameDll.extension && gameDll.indexOf("projects/") == -1) 137 { 138 projectDir = joinPath("projects", gameDll); 139 gameDll = joinPath("projects", gameDll, gameDll); 140 } 141 else 142 projectDir = gameDll; 143 144 watcher = new CompileWatcher(projectDir, null, ["d"]).run; 145 this.buildCommand = buildCommand ? buildCommand : this.buildCommand; 146 147 hotload = new HotloadableDLL(gameDll, (void* lib) 148 { 149 ErrorHandler.assertLazyExit(lib != null, "No library " ~ gameDll ~ " was found"); 150 HipremeEngineGameInit = 151 cast(typeof(HipremeEngineGameInit)) 152 dynamicLibrarySymbolLink(lib, "HipremeEngineGameInit"); 153 ErrorHandler.assertLazyExit(HipremeEngineGameInit != null, 154 "HipremeEngineGameInit wasn't found when looking into "~gameDll); 155 HipremeEngineGameDestroy = 156 cast(typeof(HipremeEngineGameDestroy)) 157 dynamicLibrarySymbolLink(lib, "HipremeEngineGameDestroy"); 158 ErrorHandler.assertLazyExit(HipremeEngineGameDestroy != null, 159 "HipremeEngineGameDestroy wasn't found when looking into "~gameDll); 160 }); 161 } 162 } 163 164 void recompileGame() 165 { 166 version(Load_DScript) 167 { 168 scope string[] errors; 169 import hip.console.log; 170 171 static void logFun(string line) 172 { 173 rawlog(line); 174 } 175 176 int status = hip.systems.compilewatcher.recompileGame(projectDir, buildCommand, &getDubError, &logFun, errors); 177 //2 == up to date 178 if(errors.length) 179 { 180 loglnError(errors); 181 foreach(err; errors) loglnError(err); 182 } 183 else 184 hotload.reload(); 185 } 186 } 187 188 void startGame() 189 { 190 import hip.view.load_scene; 191 import hip.assetmanager; 192 version(Test) 193 { 194 // addScene(new SoundTestScene()); 195 // addScene(new ChainTestScene()); 196 // addScene(new AssetTest()); 197 import hip.view.testscene; 198 import hip.console.log; 199 import hip.api.data.commons; 200 mixin LoadReferencedAssets!(["hip.view.testscene"]); 201 loadReferenced; 202 hiplog("starting test scene."); 203 addScene(new TestScene()); 204 } 205 else version(Load_DScript) 206 { 207 ErrorHandler.assertExit(HipremeEngineGameInit != null, "No game was loaded"); 208 externalScene = HipremeEngineGameInit(); 209 addScene(externalScene); 210 } 211 else version(Standalone) 212 { 213 import hip.console.log; 214 hiplog("Starting Game"); 215 externalScene = HipremeEngineMainScene(); 216 addScene(externalScene); 217 } 218 219 LoadingScene load = new LoadingScene(); 220 addScene(load, false); 221 HipAssetManager.addOnLoadingFinish(() 222 { 223 removeScene(load); 224 }); 225 } 226 227 void recompileReloadExternalScene() 228 { 229 version(Load_DScript) 230 { 231 import hip.util.array:remove; 232 import hip.console.log; 233 if(hotload) 234 { 235 shouldResetDelta = true; 236 rawlog("Recompiling game"); 237 HipTimerManager.clearSchedule(); 238 scriptInputListener.clearAll(); 239 HipremeEngineGameDestroy(); 240 scenes.remove(externalScene); 241 externalScene = null; 242 recompileGame(); // Calls hotload.reload(); 243 startGame(); 244 } 245 } 246 } 247 248 /** 249 * Adding a scene will initialize them, while checking for assets referencing for auto loading them. 250 */ 251 void addScene(AScene s, bool isOnLoadFinish = true) 252 { 253 import hip.assetmanager; 254 255 auto onLoadFn = () 256 { 257 import hip.console.log; 258 version(CustomRuntime) 259 { 260 s.preload(); 261 loglnWarn("Initializing scene ", s.getName); 262 s.initialize(); 263 scenes~= s; 264 } 265 else 266 { 267 try{ 268 s.preload(); 269 loglnWarn("Initializing scene ", s.getName); 270 s.initialize(); 271 scenes~= s; 272 } 273 catch (Error e){scriptFatalError(e);} 274 } 275 }; 276 if(isOnLoadFinish) 277 HipAssetManager.addOnLoadingFinish(onLoadFn); 278 else 279 onLoadFn(); 280 // } 281 } 282 283 void removeScene(AScene s) 284 { 285 import hip.util.array:remove; 286 remove(scenes, s); 287 } 288 289 290 bool update(float deltaTime) 291 { 292 import hip.assetmanager; 293 isInUpdate = true; 294 frames++; 295 fpsAccumulator+= deltaTime; 296 if(shouldResetDelta) 297 { 298 deltaTime = 0; 299 shouldResetDelta = false; 300 } 301 if(fpsAccumulator >= 1.0) 302 { 303 import hip.console.log; 304 // logln("FPS: ", frames); 305 frames = 0; 306 fpsAccumulator = 0; 307 } 308 import hip.console.log; 309 310 HipAudio.update(); 311 HipTimerManager.update(deltaTime); 312 HipAssetManager.update(); 313 314 version(Load_DScript) 315 { 316 if(watcher.update()) 317 recompileReloadExternalScene(); 318 } 319 dispatcher.handleEvent(); 320 dispatcher.pollGamepads(deltaTime); 321 inputListener.update(); 322 scriptInputListener.update(); 323 324 if(hasFinished || dispatcher.hasQuit) 325 return false; 326 foreach(s; scenes) 327 { 328 import hip.console.log; 329 version(CustomRuntime) 330 { 331 if(s is null) logln("SCENE IS NULL"); 332 else s.update(deltaTime); 333 } 334 else 335 { 336 try 337 { 338 if(s is null) logln("SCENE IS NULL"); 339 else s.update(deltaTime); 340 } 341 catch (Error e){scriptFatalError(e);} 342 } 343 } 344 345 return true; 346 } 347 348 version(CustomRuntime){} 349 else 350 void scriptFatalError(Throwable e, string file = __FILE__, size_t line = __LINE__, string func = __PRETTY_FUNCTION__) 351 { 352 import hip.console.log; 353 import hip.util.path; 354 loglnError(e.msg, ". Project: (", projectDir, ") at file (", e.file, ":",e.line, ")"); 355 quit(); 356 ErrorHandler.assertExit(false, "Script Fatal Error", file, line, __MODULE__, func); 357 } 358 void render() 359 { 360 version(CustomRuntime) 361 { 362 foreach (AScene s; scenes) 363 s.render(); 364 } 365 else 366 { 367 try 368 { 369 foreach (AScene s; scenes) 370 s.render(); 371 } 372 catch(Throwable e){scriptFatalError(e);} 373 } 374 HipTimerManager.render(); 375 } 376 void postUpdate() 377 { 378 dispatcher.postUpdate(); 379 isInUpdate = false; 380 } 381 382 void quit() 383 { 384 version(Load_DScript) 385 { 386 if(hotload !is null) 387 { 388 if(HipremeEngineGameDestroy != null) 389 HipremeEngineGameDestroy(); 390 scenes.length = 0; 391 externalScene = null; 392 hotload.dispose(); 393 watcher.stop(); 394 } 395 } 396 import hip.assetmanager; 397 HipAssetManager.dispose(); 398 399 } 400 }